using System;
using System.Text;
using System.Reflection;

using Tao.Lua;
using log4net;

namespace Playtime.RegionModule
{
	/// <summary>
	/// This class is responsible for executing Lua scripts
	/// </summary>
	public class Executor
	{
		/// <summary>
		/// Reference the logger instance
		/// </summary>
		private static readonly ILog mLog = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);

		IntPtr mLuaState;
		bool mLuaAllowCollectGarbage = false;

		/// <summary>
		/// The profile stance this Executor belongs to
		/// </summary>
		ProfileInstance mProfileInstance;
		
		public Executor(ProfileInstance p, bool luaAllowCollectGarbage, string scriptCode)
		{
			mProfileInstance = p;
			mLuaState = Lua.luaL_newstate();
			mLuaAllowCollectGarbage = luaAllowCollectGarbage;
			
			// setup (sandboxed) library functions			
			Lua.lua_pushcfunction(mLuaState, Lua.luaopen_base);
			Lua.lua_pushstring(mLuaState, "");
			Lua.lua_call(mLuaState, 1, 0);

			Lua.lua_pushcfunction(mLuaState, Lua.luaopen_table);
			Lua.lua_pushstring(mLuaState, "table");
			Lua.lua_call(mLuaState, 1, 0);

			Lua.lua_pushcfunction(mLuaState, Lua.luaopen_string);
			Lua.lua_pushstring(mLuaState, "string");
			Lua.lua_call(mLuaState, 1, 0);

			Lua.lua_pushcfunction(mLuaState, Lua.luaopen_math);
			Lua.lua_pushstring(mLuaState, "math");
			Lua.lua_call(mLuaState, 1, 0);

			Lua.lua_pushcfunction(mLuaState, Lua.luaopen_os);
			Lua.lua_pushstring(mLuaState, "os");
			Lua.lua_call(mLuaState, 1, 0);

			// Register the classes shared between .net and LUA
			//ScriptInstance.DefineClass(mLuaState, ScriptInstance.LuaClassName);
			//Signals.DefineClass(mLuaState, Signals.LuaClassName);
			//Signal.DefineClass(mLuaState, Signal.ClassName);
			
			// add a temporary function, for setting default modules in the sandbox environment
			Lua.lua_register(mLuaState, "register_default_modules", new Lua.lua_CFunction(RegisterDefaultModules));

			// override "dangerous" functions/methods
			Lua.lua_register(mLuaState, "print", new Lua.lua_CFunction(PerformPrintOnLog));

			// Setup the actual sourcecode to execute
			mLog.Info("[" + RegionModule.ModuleName + "]" + " Loading Core engine");
			int s = Lua.luaL_loadstring(mLuaState, Utils.GetResourceAsString("InstanceWrapper.lua"));
			if(s != 0) 
			{
				reportErrors(mLuaState, s);
				throw new PlaytimeException("Failed setting up the lua runtime environment");
			}
			
			s = Lua.lua_pcall(mLuaState, 0, 0, 0);
			if(s != 0) 
			{
				reportErrors(mLuaState, s);
				throw new PlaytimeException("Failed intializing the lua runtime environment");
			}

			// register the actual user defined code
			s = Lua.luaL_loadstring(mLuaState, scriptCode);
			if(s != 0) 
			{
				reportErrors(mLuaState, s);
				throw new PlaytimeException("Failed setting up the lua runtime environment");
			}
			
			Lua.lua_getglobal(mLuaState, "ExecuteUnsafeCode");
			Lua.lua_pushstring(mLuaState, scriptCode);
			s = Lua.lua_pcall(mLuaState, 1, 0, 0);
			if(s != 0) 
			{
				reportErrors(mLuaState, s);
			}
		}
	
		public void ExecuteInitializeHook()
		{
			Lua.lua_getglobal(mLuaState, "initialize");
			int s = Lua.lua_pcall(mLuaState, 0, 0, 0);
			if(s != 0) 
			{
				reportErrors(mLuaState, s);
			}
		}
		
		private static int PerformPrintOnLog(IntPtr luaState)
        {
            int argc = Lua.lua_gettop(luaState);

			StringBuilder logMessage = new StringBuilder("[" + RegionModule.ModuleName + "]" + " print:");
            for (int n = 1; n <= argc; n++)
            {
				if(n>1)
                	logMessage.Append("\t");
				logMessage.Append(Lua.lua_tostring(luaState, n));
            }
            mLog.Debug(logMessage.ToString());
            Console.WriteLine(logMessage.ToString());
			return 0; // number of return values
        }
		
//		AttributeInstance Values = new AttributeInstance();
//		Module api = new AttributeValue.LuaApi();
		
		private int RegisterDefaultModules(IntPtr luaState)
		{
			mLog.Debug("[" + RegionModule.ModuleName + "]" + " RegisterDefaultModules");

			if(mLuaAllowCollectGarbage) 
			{
			}
			foreach(IAttributeInstance v in mProfileInstance.Values.Values)
			{
				//TODO: This registers the API per instance, it really should be per type
				v.RegisterApi(luaState);
				v.DefineObjectAsField(luaState);
			}
//			api.Register(luaState);	
//			Values.DefineField(luaState, "Str");

//			foreach(Addins.ModuleNode moduleNode in mMainModule.Modules.Values)
//			{
//				if(moduleNode.IsDefault)
//				{
//					Api.IModule module = (Api.IModule)moduleNode.CreateInstance();
//					module.Register(luaState);
//					mRegisteredModules.Add(moduleNode.Name, module);
//					
//					if(moduleNode.IsClass)
//						InvokeDefineClassOnType(module.GetType(), luaState, moduleNode.Name);
//				}
//			}
			return 0;
		}

		private static void reportErrors(IntPtr L, int status)
		{
			if ( status!=0 ) 
			{
				mLog.Warn("[" + RegionModule.ModuleName + "]" + " -- script error: " + Lua.lua_tostring(L, -1));
				Lua.lua_pop(L, 1); // remove error message
			}
		}
	}
}

